# resume_filter/matcher/embedding_matcher.py (混合匹配升级版)

import numpy as np
from typing import Dict, Any
from .base_matcher import BaseMatcher  # 假设您有这个基类
from openai import OpenAI
from config.API import api          # 在您的项目中，请使用此行



class EmbeddingMatcher(BaseMatcher):
    def __init__(self, model_name: str = "text-embedding-v1", field_weights: Dict[str, float] = None):
        """
        :param model_name: 使用的大模型embedding模型名称
        :param field_weights: [备用] 当JD中不提供权重时使用的默认权重
        """
        self.client = OpenAI(api_key=api, base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")
        self.model_name = model_name
        
        # 默认备用权重
        self.default_field_weights = field_weights or {
            "skills": 0.4,
            "experience_years": 0.3,
            "education_level": 0.1,
            "soft_skills": 0.1,
            "languages": 0.05,
            "other_tags": 0.05
        }

        # [新增] 为有序类别字段定义序级，用于规则匹配
        self.education_order = {
            "不限": 0, "": 0, None: 0, # 处理各种空值
            "大专": 1,
            "本科": 2,
            "硕士": 3,
            "博士": 4
        }

    # ==========================================================================
    # 核心匹配逻辑：三种不同的匹配方法
    # ==========================================================================

    def _match_semantic(self, resume_text: str, jd_text: str) -> float:
        """为技能等【语义】字段执行向量嵌入和余弦相似度计算。"""
        if not jd_text: return 1.0  # 如果JD无要求，则视为完全匹配
        if not resume_text: return 0.0 # 如果JD有要求但简历没有，则不匹配

        embed_resume = self._get_embedding(resume_text)
        embed_jd = self._get_embedding(jd_text)
        return self._cosine_similarity(embed_resume, embed_jd)

    def _match_numerical(self, resume_value: int, jd_value: int) -> float:
        """
        为工作年限等【数值】字段进行比较。
        简历值 >= JD要求值，则得满分。否则按比例得分，并设置一个下限。
        """
        if jd_value == 0:
            return 1.0  # JD不要求工作年限，视为满足
        if resume_value is None or resume_value < 0:
            return 0.0
        
        if resume_value >= jd_value:
            return 1.0
        else:
            # 简历年限小于要求，按比例给分 (例如 JD要5年，简历3年，得 3/5=0.6分)
            # 可以增加一个柔性因子，比如即使差一点也给一些基础分
            score = float(resume_value) / jd_value
            return score

    def _match_categorical(self, resume_category: str, jd_category: str) -> float:
        """为学历等【有序类别】字段进行比较。"""
        jd_level = self.education_order.get(jd_category, 0)
        resume_level = self.education_order.get(resume_category, 0)
        
        # 只要简历学历不低于JD要求，就得满分
        return 1.0 if resume_level >= jd_level else 0.0

    # ==========================================================================
    # 主匹配函数 (Match) - 智能分发器
    # ==========================================================================

    # matcher/embedding_matcher.py

    def match(self, resume: Dict[str, Any], jd: Dict[str, Any]) -> Dict[str, Any]:
        """
        [最终修正版] 根据字段类型，分发到不同的匹配方法。
        能正确处理 'skills' 权重对应 'hard_skills' 数据。
        """
        self._check_inputs(resume, jd)
        weights_to_use = self._get_weights(jd)

        detail_scores = {}
        weighted_score_sum = 0.0

        for field_from_weights, weight in weights_to_use.items():
            if weight == 0:
                detail_scores[field_from_weights] = 0.0
                continue

            score = 0.0
            print(f"INFO: Matching dimension '{field_from_weights}' with weight {weight:.2f}...")

            # --- [核心修正] 智能分发逻辑 ---

            # 如果权重文件中的维度是 'skills'，我们实际要匹配的是 'hard_skills'
            if field_from_weights == 'skills':
                field_to_match = 'hard_skills' # 将权重维度映射到实际数据字段
                resume_text_list = resume.get('skills', {}).get(field_to_match)
                jd_text_list = jd.get('skills', {}).get(field_to_match)
                
                resume_text = self._flatten_field_text(resume_text_list)
                jd_text = self._flatten_field_text(jd_text_list)
                
                score = self._match_semantic(resume_text, jd_text)
                print(f"  -> Type: Semantic (mapping '{field_from_weights}' -> '{field_to_match}'). Score: {score:.2f}")

            elif field_from_weights == 'soft_skills':
                # 'soft_skills' 维度直接对应 'soft_skills' 数据字段
                field_to_match = 'soft_skills'
                resume_text_list = resume.get('skills', {}).get(field_to_match)
                jd_text_list = jd.get('skills', {}).get(field_to_match)
                
                resume_text = self._flatten_field_text(resume_text_list)
                jd_text = self._flatten_field_text(jd_text_list)
                
                score = self._match_semantic(resume_text, jd_text)
                print(f"  -> Type: Semantic (direct mapping). Score: {score:.2f}")

            elif field_from_weights == 'experience_years':
                resume_val = int(resume.get(field_from_weights, 0))
                jd_val = int(jd.get(field_from_weights, 0))
                score = self._match_numerical(resume_val, jd_val)
                print(f"  -> Type: Numerical. Score: {score:.2f} ({resume_val} vs {jd_val})")
            
            elif field_from_weights == 'education_level':
                score = self._match_categorical(str(resume.get(field_from_weights, "")), str(jd.get(field_from_weights, "")))
                print(f"  -> Type: Categorical. Score: {score:.2f} ('{resume.get(field_from_weights)}' vs '{jd.get(field_from_weights)}')")
            
            else: # languages, other_tags
                resume_text = self._flatten_field_text(resume.get(field_from_weights))
                jd_text = self._flatten_field_text(jd.get(field_from_weights))
                score = self._match_semantic(resume_text, jd_text)
                print(f"  -> Type: Semantic. Score: {score:.2f}")
            
            detail_scores[field_from_weights] = score
            weighted_score_sum += score * weight

        total_score = weighted_score_sum
        passed = total_score >= 0.7 

        return {
            "total_score": total_score,
            "details": detail_scores,
            "passed": passed,
            "weights_used": weights_to_use
        }
    def _get_weights(self, jd: Dict[str, Any]) -> Dict[str, float]:
        """获取本次匹配要使用的权重字典。"""
        if "dimension_weights" in jd and jd["dimension_weights"]:
            print("INFO: 检测到JD中提供动态权重，将使用该权重进行匹配。")
            jd_weights = jd["dimension_weights"]
            total_jd_weight = sum(jd_weights.values())
            if total_jd_weight > 0:
                weights = {field: val / total_jd_weight for field, val in jd_weights.items()}
                print("  -> 使用的动态权重:", {k: f"{v:.2f}" for k, v in weights.items()})
                return weights
        
        print("WARN: JD中未提供动态权重或权重总和为0，将使用默认权重。")
        print("  -> 使用的默认权重:", self.default_field_weights)
        return self.default_field_weights

    def _flatten_field_text(self, field_value: Any) -> str:
        """将字段内容转成纯文本字符串，用于生成embedding。"""
        if not field_value: return ""
        if isinstance(field_value, (int, float)): return str(field_value)
        if isinstance(field_value, str): return field_value
        if isinstance(field_value, list): return " ".join(str(item) for item in field_value)
        if isinstance(field_value, dict): return " ".join(str(v) for v in field_value.values())
        return str(field_value)

    def _get_embedding(self, text: str) -> np.ndarray:
        """调用大模型获取文本embedding向量。"""
        if not text.strip(): return np.zeros(1536)
        response = self.client.embeddings.create(model=self.model_name, input=[text])
        return np.array(response.data[0].embedding)

    def _cosine_similarity(self, vec1: np.ndarray, vec2: np.ndarray) -> float:
        """计算两个向量的余弦相似度。"""
        if np.all(vec1 == 0) or np.all(vec2 == 0): return 0.0
        return float(np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2)))


if __name__ == '__main__':
    # 新增的、用于验证混合匹配的测试用例
    import json
    
    matcher = EmbeddingMatcher()

    # --- 准备模拟数据 ---
    # 简历：5年经验，硕士
    resume = {"skills": "精通Java和Spring", "experience_years": 5, "education_level": "硕士"}
    
    # JD 1: 要求3年，本科 -> 应该高分通过
    print("="*20, "测试场景 1: 候选人完全满足并超过要求", "="*20)
    jd1 = {"skills": "需要Java, Spring Boot", "experience_years": 3, "education_level": "本科"}
    result1 = matcher.match(resume, jd1)
    print("\n结果1:", json.dumps(result1, indent=2))
    assert result1['details']['experience_years'] == 1.0, "年限匹配错误"
    assert result1['details']['education_level'] == 1.0, "学历匹配错误"
    
    # JD 2: 要求8年，本科 -> 年限不满足，应该分数较低
    print("\n" + "="*20, "测试场景 2: 年限不满足", "="*20)
    jd2 = {"skills": "需要Java, Spring Boot", "experience_years": 8, "education_level": "本科"}
    result2 = matcher.match(resume, jd2)
    print("\n结果2:", json.dumps(result2, indent=2))
    assert result2['details']['experience_years'] == 5/8, "年限比例分计算错误"
    
    # JD 3: 要求5年，博士 -> 学历不满足
    print("\n" + "="*20, "测试场景 3: 学历不满足", "="*20)
    jd3 = {"skills": "需要Java, Spring Boot", "experience_years": 5, "education_level": "博士"}
    result3 = matcher.match(resume, jd3)
    print("\n结果3:", json.dumps(result3, indent=2))
    assert result3['details']['education_level'] == 0.0, "学历不满足时应为0分"

    print("\n✅ 所有混合匹配测试用例通过！")